﻿import com.mosesSupposes.fuse.FuseKitCommon;

/**
* Fuse Kit 2
* Copyright (c) 2006 Moses Gunesch, MosesSupposes.com
* 
* Distributed under MIT Open Source License, see Fuse-Kit-License.html (in fuse package directory)
* Easing Equations (c) 2003 Robert Penner used by permission, see PennerEasing
* Visit http://www.mosessupposes.com/Fuse
* 
* @ignore
*
* Pass this class to {@link com.mosesSupposes.fuse.ZigoEngine#register} or {@link com.mosesSupposes.fuse.ZigoEngine#simpleSetup} 
* to enable Fuse-style Object Syntax parsing capability with {@link com.mosesSupposes.fuse.ZigoEngine#doTween}. 
* Note that registering Fuse is not required to use this feature.
* @usage 
* <pre>ZigoEngine.doTween({ target:clip1, scale:200, ease:Strong.easeIn, time:"00:50", startAt:"01:50" });</pre>
* <br><i>Most methods and properties are excluded here - see class file for further documentation.</i>
* @author	Moses Gunesch / MosesSupposes.com
* @version	2.1.3r1
*/
class com.mosesSupposes.fuse.FuseItem {

	/**
	 * @exclude
	 * Unique identifier used by ZigoEngine.register
	 */ 
	public static var registryKey:String = 'fuseItem';
	
	/**
	 * When true, known tween properties may omit underscores (x will convert to _x, etc.)
	 */ 
	public static var ADD_UNDERSCORES:Boolean = true; 
	
	/**
	 * @exclude
	 * FuseItem's current index in the engine's array stack. index is 0-based like an Array. 
	 * Can be set externally at any time by parent Fuse if items are spliced or reordered.
	 */ 
	public var _nItemID:Number;
	
	/**
	 * @exclude
	 * Private internal play-state value. -1=stopped, 0=paused, 1=playing, 2=special flag used during the addTweens cycle.
	 */ 
	public var _nPlaying:Number = -1;

	/**
	 * @exclude
	 * Extremely temporary record of the target lists of doTween calls only while the calls are made, 
	 * used to cross-track interruptions and determine if a Fuse is interrupting itself (in which case it should not auto-stop).
	 */
	public var _oTwBeingAdded : Object;
	
	/**
	 * @exclude
	 * An internal reference to the ZigoEngine class. Fuse + FuseItem may be used without ZigoEngine so an import statement 
	 * is not used to avoid unecessary filesize when the engine is not needed.
	 */
	private static var _ZigoEngine:Function;
	
	/**
	 * @exclude
	 * Original action object or array passed by user
	 */
	private var _initObj:Object;
	
	/**
	 * @exclude
	 * Unique ID of the FuseItem's parent Fuse instance.
	 */
	private var _nFuseID:Number;
	
	/**
	 * @exclude
	 * Internal memory flag indicating start properties have been preset during current play cycle.
	 */ 
	private var _bStartSet:Boolean = false;
	
	/**
	 * @exclude
	 * Internal flag indicating whether the FuseItem has triggered advance due to a trigger:true value in a profile.
	 */ 
	private var _bTrigger:Boolean = false;
	
	/**
	 * @exclude
	 * Internal array queue that is progressively deleted upon tween completion to track FuseItem completion. 
	 * (Public for access by Fuse.getActiveTargets) 
	 */
	private var _aTweens:Array;
	
	/**
	 * @exclude
	 * Storage tank for FuseItem elements that are not handled by tweens: command, label, aEvents, and delay 
	 * if no tweens found. (Public for access by Fuse.currentLabel, Fuse.skipTo)
	 */ 
	private var _oElements:Object;
	
	/**
	 * @exclude
	 * Internal storage array of parsed action objects, meaning any object with tweenable elements or elements where tweening may or may not occur.
	 */ 
	private var _aProfiles:Array;
	
	/**
	 * @exclude
	 * Verbose string listing the general contents of the FuseItem, viewable by calling traceItems on the parent Fuse instance.
	 */
	private var _sImage:String;
	
	/**
	 * @exclude
	 * An internal collection tank for variables used across multiple parseProfile calls as the FuseItem is being instantiated. Deleted when constructor is finished.
	 */ 
	private var _oTemps:Object;
	
	/**
	 * @exclude
	 * An internal collection of FuseItem instances generated when Fuse Object Syntax is used with <code>ZigoEngine.doTween</code>. 
	 * These otherwise untracked instances are given an ID of -1 and are auto-deleted by the FuseItem class upon completion using <code>removeInstance</code>.
	 */
	private static var _aInstances:Array;
	
	/**
	* This method needn't be called as method call is rerouted here automatically upon <code>ZigoEngine.doTween</code>.
	* @return		comma-delimited string listing properties successfully added to the engine.
	*/
	public static function doTween():String
	{
		for (var i:String in arguments) {
			if (typeof arguments[i]=='object') {
				if (_aInstances==undefined) _aInstances = new Array();
				var o:FuseItem = new FuseItem(_aInstances.length, arguments[i], -1);
				return o.startItem();
			}
		}
	}

	// --------------------------------------------------------------------//
	//	1. Fuse-access methods
	//	2. Constructor & parseProfile
	//	3. doTweens, tween event handlers, utils 
	// --------------------------------------------------------------------//
	
	// 1.
	
	/**
	* @exclude
	* label property queried by Fuse
	* @return		label string if FuseItem contains a label property
	*/ 
	public function get label():String
	{
		return _oElements.label;
	}
	
	/**
	* @exclude
	* Fuse needs to retrieve original objects passed for Array methods that return them
	* @return		true if trigger has been fired within currently playing item
	*/ 
	public function hasTriggerFired():Boolean
	{
		return (_bTrigger==true);
	}
	
	
	/**
	* @exclude
	* Fuse needs to retrieve original objects passed for Array methods that return them
	* @return		original action object as passed by user
	*/ 
	public function getInitObj():Object
	{
		return (_initObj);
	}
	
	/**
	* @exclude
	* Fuse calls this to get additional playing or paused targets to add to defaults when getActiveTargets is called on a Fuse.
	* @return		a list of target objects or empty array
	*/ 
	public function getActiveTargets(targetList:Array):Array
	{
		if (!(_aTweens.length>0)) return targetList;
		var found:Boolean = false;
		for (var i:String in _aTweens) {
			// filter duplicates
			for (var j:String in targetList) { 
				if (targetList[j]==(_aTweens[i]).targ) {
					found = true;
					break;
				}
			}
			if (found==false) {
				targetList.unshift((_aTweens[i]).targ);
			}
		}
		return targetList;
	}
	
	/**
	 * @exclude
	 */
	public function toString():String { return String((_sID())+':'+_sImage); }
	
	/**
	* @exclude
	* Called by parent Fuse to retrieve simple freestanding delays (note that in certain cases 
	* FuseItems use ZigoEngine tweens to time complex delays, such as those that appear within 
	* an action group). Such non-tweened delays are centralized into the parent Fuse, which runs 
	* a setInterval timer and handles pause/resume behavior for that delay.
	* @param scope		current default scope set in parent Fuse instance
	* @return			delay in seconds
	*/
	public function evalDelay(scope:Object):Number 
	{
		var d:Object = _oElements.delay;
		if (d instanceof Function) {
			d = d.apply((_oElements.delayscope!=undefined) ? _oElements.delayscope : scope);
		}
		if (typeof d=='string') d = (parseClock(String(d)));
		if (_global.isNaN(Number(d))==true) return 0;
		return Number(d);
	}
	
	/**
	* @exclude
	* Called by parent Fuse to play the FuseItem, including triggering commands, firing callbacks not associated with tweens, and starting tweens.
	* @param targs - an array of default targets currently set in parent Fuse instance
	* @param scope - current default scope set in parent Fuse instance
	* @return		returns successfully tweened props for doTween()
	*/
	public function startItem(targs:Array, scope:Object, duration:Number, easing:Object):String
	{
		_ZigoEngine = _global.com.mosesSupposes.fuse.ZigoEngine;
		var fuse:Object = _global.com.mosesSupposes.fuse.Fuse;
		var outputLevel:Number = (fuse!=undefined) ? fuse.OUTPUT_LEVEL : _ZigoEngine.OUTPUT_LEVEL;
		// Fuse command like 'pause' or 'stop'. v2.0: Only delay,scope,args are retained for items containing command.
		if (_oElements.command!=null) { 
			var cs:Object = (_oElements.scope || scope); // profile scope used for eval only. command always sent to parent fuse
			var command:String = (_oElements.command instanceof Function) ? String(_oElements.command.apply(cs)) : String(_oElements.command); // user can pass a Delegate to scope this
			// IMPORTANT: args param is not used during eval, args are passed to Fuse, primarily for use with skipTo(index/label)
			var args:Array = (_oElements.args instanceof Function) ? _oElements.args.apply(cs) : _oElements.args;
			var valid:Boolean = FuseKitCommon._validateFuseCommand(command, (_aProfiles.length>0), (args!=null && !(args instanceof Array && args.length==0)), outputLevel, false); 
			if (valid==true) {
				_nPlaying = 1;
				if (!(args instanceof Array)) args = (args==null) ? [] : [args];
				dispatchRequest(String(command), args);
			}
			if (valid==false || command=='setStartProps') {
				complete();
			}
			return null; // From docs: "Actions containing a command property may ONLY contain the additional properties: scope, args, label, delay."
		}
		// Changed order to deal with skipLevel. Now we tween first then fire callbacks and events.
		if (_aTweens.length>0) this.stop();
		_ZigoEngine.addListener(this); // For monitoring targets that go missing. Only one corresponding remove, in stop(), which is called by complete().
		_nPlaying = 2; // Special flag meaning doTweens is running, important for avoiding onTweenInterrupt/End conflicts.
		var propsAdded:String = null;
		if (_aProfiles.length>0) {
			if (_ZigoEngine==undefined) {
				FuseKitCommon.error('112');
			}
			else {
				propsAdded = doTweens(targs, scope, duration, easing, false, false);
			}
		}
		// do not move. Callbacks can contain pause or other play commands, so this is checked afterward
		_nPlaying = 1; 
		
		// Non-tween callbacks & events (skipped if skipLevel is 2 and all tweens failed)
		var fa:Array = _oElements.aEvents;
		for (var i:String in fa) {
			if (propsAdded==null && _aTweens.length>0 && (fa[i]).skipLevel==2) continue;
			fireEvents(fa[i],scope,outputLevel,targs);
		}
		if (propsAdded==null && !(_aTweens.length>0) && _nPlaying==1) {
			if (outputLevel==3) FuseKitCommon.output((_sID())+' no tweens added - item done. [getTimer()='+getTimer()+']');
			complete();
		}
		return propsAdded;
	}
	
	/**
	* @exclude
	* Called by parent Fuse to stop the FuseItem's tweens. If currently playing, the internal method onStop is called which clears current tween list and associated listeners.
	*/
	public function stop():Void
	{
		// (for onTweenInterrupt)
		var doOnStop:Boolean = (_nPlaying>-1); 
		_nPlaying = -1;
		// active stop: clear this item's tweens and remove listeners
		if (doOnStop==true) onStop();
		_ZigoEngine.removeListener(this);
	}
	
	// -- private --
	
	/**
	* @exclude
	*/
	private static function removeInstance(id:Number):Void
	{
		FuseItem(_aInstances[id]).destroy();
		delete _aInstances[id];
	}

	
	/**
	* @exclude
	* Clears active elements. (Fuse event reverse-subscribed by parent Fuse)
	*/
	private function onStop():Void 
	{
		_bStartSet = false;
		for (var i:String in _aTweens) {
			var to:Object = _aTweens[i];
			to.targ.removeListener(this);
			_ZigoEngine.removeTween(to.targ, to.props);
			delete _aTweens[i];
		}
		delete _aTweens;
		_bTrigger = false;
	}
	
	/**
	* @exclude
	* An event dispatched by parent Fuse to all child FuseItems to preset tween start-values.
	* @param o		event object containing a .filter property which can specify exclusion from the call
	*/
	private function evtSetStart(o:Object):Void
	{
		// no starts to set or Fuse is about to play this item.
		if (_sImage.indexOf('StartProps:')==-1 || o.curIndex==_nItemID) {
			return;
		}
		if (o.all!=true) {
			var match:Boolean = false;
			for (var i:String in o.filter) {
				if (Number(o.filter[i])==_nItemID || String(o.filter[i])==_oElements.label) match = true;
			}
			if (match==false) {
				return;
			}
		}
		_nPlaying = 2; // Special flag meaning doTweens is running, important for avoiding onTweenInterrupt/End conflicts.
		doTweens(o.targs, o.scope, null, null, true, false);
		_nPlaying = -1;
		// flag removed when parent Fuse stopped.
		_bStartSet = true;
	}

	
	/**
	* @exclude
	* Called by parent Fuse to pause or resume the FuseItem's tweens and tweened delays. During resume it is not assumed 
	* that targets and pauses are intact, each is checked and the internal tween array is trimmed if items have gone missing.
	* @param resume		 indicates whether it is a pause or resume call
	*/
	public function pause(resume:Boolean):Void
	{
		if (_nPlaying==-1) return;
		_nPlaying = ((resume==true) ? 1 : 0);
		for (var i:String in _aTweens) {
			var o:Object = _aTweens[i];
			var t:Object = o.targ;
			var p:Object = o.props;
			if (resume==true) {
				// Is pause intact? Target/prop could have been manipulated since it was paused here.
				var missing:Array = [];
				var oldTL:Number = _aTweens.length;
				for (var j:String in p) {
					if (_ZigoEngine.isTweenPaused(t,p[j])==false) missing.push(p[j]);
				}
				if (missing.length>0) {
					onTweenEnd({__zigoID__:o.targZID, props:missing, isResume:true});
				}
				if (_aTweens.length==oldTL) { // otherwise onTweenEnd removed the tween
					t.addListener(this);
					_ZigoEngine.unpauseTween(t, o.props);
				}
			}
			else {
				t.removeListener(this);
				_ZigoEngine.pauseTween(t, o.props);
			}
		}
		if (resume==true && !(_aTweens.length>0)) { // no tweens.
			complete();
		}
		else if (resume==true) {
			_ZigoEngine.addListener(this);
		}
		else {
			_ZigoEngine.removeListener(this);
		}
	}
	
	/**
	* @exclude
	* Called by parent Fuse during fastForward to advance all animations. 
	* Do not fire callbacks/events. 
	* Parent fuse handles advance.
	* @param ignore		integrate polymorphically with Fuse.fastForward which needs null for first param
	* @param targs		parent fuse default
	* @param scope		parent fuse default (only applies to runtime eval. to retrieve tween params, callbacks not fired)
	*/
	public function fastForward(ignore:Object, targs:Array, scope:Object):Void
	{
		if (_nPlaying==1) {
			for (var i:String in _aTweens) {
				var o:Object = _aTweens[i];
				var t:Object = o.targ;
				t.removeListener(this);
				_ZigoEngine.ffTween(t, o.props, true);
			}
			return;
		}
		if (_nPlaying==2) {
			FuseKitCommon.error('125',_nItemID);
		}
		_nPlaying = 2;
		doTweens(targs, scope, null, null, false, true);
		stop();
	}
	
	/**
	* @exclude
	* Called by parent Fuse to clear FuseItem instance's memory and listeners during Fuse's destroy cycle.
	*/
	public function destroy():Void
	{
		var doRemove:Boolean = (_nPlaying>-1);
		_nPlaying = -1;
		for (var i:String in _aTweens) {
			var o:Object = _aTweens[i];
			o.targ.removeListener(this);
			if (doRemove==true) _ZigoEngine.removeTween(o.targ, o.props);
			delete _aTweens[i];
		}
		for (var j:String in this) {
			delete this[j];
		}
	}
	
	/**
	* @exclude
	* @param type	fuse method
	* @param args	method params
	*/
	private function dispatchRequest(type:String, args:Array):Void
	{// avoids import of Fuse class since FuseItem can be used with ZigoEngine as an Object Syntax extension
		var f:Object = _global.com.mosesSupposes.fuse.Fuse.getInstance(_nFuseID); 
		if (!(args instanceof Array) && args!=null) args = (new Array(args));
		Function(f[type]).apply(f, args);
	}
	
	// 2.
	
	/**
	* @exclude
	* Concise string ID representing the parent Fuse's fixed ID and the FuseItem's current index in the Fuse, such as "Fuse#0>Item#0"
	*/
	private function _sID():String // like "Fuse#0>Item#0"
	{
		var str:String;
		if (_nFuseID==-1) {
			str = '-One-off tween ';
		}
		else {
			str = (_global.com.mosesSupposes.fuse.Fuse.getInstance(_nFuseID)).getHandle();
		}
		str+='>Item #'+String(_nItemID);
		if (_oElements.label!=undefined) str+=' "'+_oElements.label+'"';
		return str;
	}
	
	/**
	* @exclude
	* Constructor - Attempts to parse action(s) into <code>_oElements</code> and <code>_aProfiles</code>, building the verbose string ID <code>_sImage</code> in the process.
	* @param id			current index of FuseItem in parent Fuse instance
	* @param o			action object or Array of action objects pushed into the Fuse for parsing
	* @param fuseID		permanent unique ID of parent Fuse stored in _nFuseID
	*/
	public function FuseItem (id:Number, o:Object, fuseID:Number)
	{
		_ZigoEngine = _global.com.mosesSupposes.fuse.ZigoEngine;
		this._nItemID = id;
		this._nFuseID = fuseID;
		this._initObj = o;
		
		_aProfiles = [];
		_oElements = { aEvents:[] };
		_oTemps = {};
		if (!(o instanceof Array)) o = [o];
		var fuse:Object = _global.com.mosesSupposes.fuse.Fuse;
		_oTemps.outputLevel = (fuse!=undefined) ? fuse.OUTPUT_LEVEL : _global.com.mosesSupposes.fuse.ZigoEngine.OUTPUT_LEVEL;
		
		// v2 Command actions can only contain delay, label, scope, args and may not be nested in groups.
		if (o.length==1) {
			var o0:Object = o[0];
			var obj:Object = (o0.action!=undefined) ? o0.action : o0;
			if (obj.__buildMode!=true && obj.command!=undefined) {
				_oElements.command = obj.command;
				_oElements.scope = obj.scope; // v2.0: changed commandscope & commandargs to scope, args.
				_oElements.args = obj.args;
				_sImage = ' Elements:['+('command'+((typeof obj.command=='string') ? ':"'+obj.command+'", ' : ', '));
//				if (obj.label!=undefined && typeof obj.label=='string') {
//					_sImage+=('label, ');
//					_oElements.label = obj.label; // one label per Command Action
//				}
				if (obj.delay!=undefined) {
					_sImage+='delay, ';
					_oElements.delay = obj.delay; // one delay per Command Action
				}
				_sImage=_sImage.slice(0,-2)+']';
				if (obj.func!=undefined && _oTemps.outputLevel>0) FuseKitCommon.error('113');
				return;
			}
		}
		
		// persistant vars for looping through actions
		_oTemps.sImgS = '';
		_oTemps.sImgE = '';
		_oTemps.sImgB = '';
		_oTemps.afl = 0;
		_oTemps.ael = 0;
		_oTemps.twDelayFlag = false;
		_oTemps.nActions = o.length;
		_oTemps.fuseProps = FuseKitCommon._fuseprops();
		_oTemps.cbProps = FuseKitCommon._cbprops();
		_oTemps.sUP = FuseKitCommon._underscoreable();
		_oTemps.sCT = FuseKitCommon._cts();
		_oTemps.bTriggerFound = false;
		
		// Parse each profile.
		for (var i:String in o) {
			var item:Object = o[i];
			if (item.label!=undefined && typeof item.label=='string') _oElements.label = item.label; // one string-only label per FuseItem
			var aap:Object;
			var bApplied:Boolean = Boolean(typeof item.action=='object');
			if (bApplied==true) {
				var a:Array = (item.action instanceof Array) ? item.action : [ item.action ]; // support action array
				aap = { // applied-action profile, these props are mixed into the action during parseProfile.
					delay:item.delay,
					target:item.target,
					addTarget:item.addTarget,
					label:item.label,
					trigger:item.trigger
				};
				for (var j:String in a) {
					var oPr:Object = parseProfile(a[j], aap);
					if (oPr!=undefined) {
						_aProfiles.unshift(oPr);
					}
				}
			}
			else {
				var a:Object = item;
				var oPr:Object = parseProfile(a, aap);
				if (oPr!=undefined) {
					_aProfiles.unshift(oPr);
				}
			}
		}
		
		// build string image (if a command was passed in, this happens up top.)
		_sImage = '';
		var str:String = '';
//		if (_oElements.label!=undefined) str+=('label, ');
		if (_oTemps.afl>0) str+= ((_oTemps.afl>1) ? _oTemps.afl+' callbacks, ' : 'callback, ');
		if (_oElements.delay!=undefined || _oTemps.twDelayFlag==true) str+='delay, ';
		if (_oTemps.bTriggerFound==true) str+='trigger, ';
		if (_oTemps.ael>0) str+= ((_oTemps.ael>1) ? _oTemps.ael+' events, ' : 'event, ');
		if (str!='') _sImage+=' Elements:['+(str.slice(0,-2))+']';
		if (_oTemps.sImgS!='') _sImage+= ' StartProps:['+(_oTemps.sImgS.slice(0,-2))+']'; // careful: "StartProps:" is checked in evtSetStart
		if (_oTemps.sImgE!='') _sImage+= ' Props:['+(_oTemps.sImgE.slice(0,-2))+']';
		if (_oTemps.sImgB!='') _sImage+= ' Simple Syntax Props:['+(_oTemps.sImgB.slice(0,-1))+']';
		if (_sImage.slice(-2)==', ') _sImage = _sImage.slice(0,-2);
		delete _oTemps;
	}
	
	/**
	* @exclude
	* Used by constructor to parse each action object passed (multiple objects can be passed in an Array for simultaneous action groups). 
	* The parsing process is a fairly complex procudure that blocks tween start & end properties, tags 'auto-fill' end properties in which 
	* only a start value was passed, extracts freestanding items that do not require tweens, and generates proxy tweens used to handle delays within groups.
	* @param obj			the action object to be parsed into a profile and pushed into _aProfiles
	* @param aap			Stands for 'applied action profile' object. When an 'applied action' is encountered, this override profile mixes properties into the resulting profile.
	* @return				parsed profile object
	*/
	private function parseProfile(obj:Object, aap:Object):Object 
	{
		var i:String, j:String, k:String;
		// Build Mode (simple syntax) objects
		if (obj.__buildMode==true) {
			if (obj.command!=undefined) {
				if (obj.command=='delay') { // addCommand('delay',Number)
					_oElements.delay = obj.commandargs;
				}
				else if (obj.command=='trigger') {
					if (_oTemps.bTriggerFound==false) { // only one trigger is allowed per FuseItem
						_oTemps.bTriggerFound = true;
						return { trigger:obj.commandargs, _doTimer:true };
					}
					else if (_oTemps.outputLevel>0) {
						FuseKitCommon.error('124', (_sID()), obj.commandargs);
					}
				}
				else {
					_oElements.command = obj.command;
					_oElements.args = obj.commandargs;
				}
			}
			if (obj.func!=undefined) {
				_oTemps.afl++;
				_oElements.aEvents.unshift({ scope:obj.scope, func:obj.func, args:obj.args });
			}
			if (obj.tweenargs!=undefined) {
				_oTemps.sImgB += (obj.tweenargs[1].toString()+','); // (allowing duplicates in simple syntax props image to save code, since it could be comma-delim str)
				return obj;
			}
			return null;
		}
		var oPr:Object = {
			delay:(aap.delay!=undefined) ? aap.delay : obj.delay,
			ease:obj.ease,
			seconds:obj.seconds,
			event:obj.event,
			eventparams:obj.eventparams,
			skipLevel:(typeof obj.skipLevel=='number' && obj.skipLevel>=0 && obj.skipLevel<=2) ? obj.skipLevel : _ZigoEngine.SKIP_LEVEL, // correct early for use in FuseItem.doTweens
			roundResults:obj.roundResults,
			oSP:{},
			oEP:{},
			oAFV:{}
		};
		// trigger
		var trigger:Object = ((aap.trigger!=undefined) ? aap.trigger : obj.trigger); // can be true or number, not parsed until doTweens
		if (trigger!=undefined) {
			if (_oTemps.bTriggerFound==false) { // only one trigger is allowed per FuseItem
				oPr.trigger = trigger;
				_oTemps.bTriggerFound = true;
			}
			else if (_oTemps.outputLevel>0) {
				FuseKitCommon.error('124',(_sID()),trigger);
			}
		}
		// synonyms
		if (oPr.delay==undefined) oPr.delay = obj.startAt; 
		if (oPr.ease==undefined) oPr.ease = obj.easing; // synonym
		if (oPr.seconds==undefined) oPr.seconds = ((obj.duration!=undefined) ? obj.duration : obj.time); // synonym

		// applied action target param overrides action target param
		if (aap.target!=undefined) oPr.target = ((aap.target instanceof Array) ? aap.target : [aap.target]);
		else if (obj.target!=undefined) oPr.target = ((obj.target instanceof Array) ? obj.target : [obj.target]);
		// applied action addTarget param adds to action addTarget param (don't change order)
		if (obj.addTarget!=undefined) oPr.addTarget = ((obj.addTarget instanceof Array) ? obj.addTarget : [obj.addTarget]);
		if (aap.addTarget!=undefined) {
			if (oPr.addTarget==undefined) oPr.addTarget = ((aap.addTarget instanceof Array) ? aap.addTarget : [aap.addTarget]);
			else oPr.addTarget = ((oPr.addTarget instanceof Array) ? (oPr.addTarget.concat(aap.addTarget)) : ((new Array(oPr.addTarget)).concat(aap.addTarget)));
		}
		var bTwFlag:Boolean = false;
		for (j in obj) {
			var v:Object = obj[j];
			if ((_oTemps.cbProps).indexOf('|'+j+'|')>-1) { // a callback-object prop
				if (j!='skipLevel') oPr[j] = v;
				continue;
			}
			if ((_oTemps.fuseProps).indexOf('|'+j+'|')>-1) {// skip adding fuse props which are handled above. animation properties remain. 
				if (j=='command' && _oTemps.nActions>1 && _oTemps.outputLevel>0) {
					FuseKitCommon.error('109',String(v), true); // command within group error
				}
				continue;
			}
			if (typeof v=='object') {
				var copy:Object = (v instanceof Array) ? ([]) : {};
				for (k in v) copy[k] = v[k];
				v = copy;
			}
			var se:Object;
			var seCP:Object;
			if (j.indexOf('start_')==0) { // added undescore here v2.1.1r1, if no issues come up remove this comment later.
				if (j=='start_controlX' || j=='start_controlY' || j.indexOf('_bezier_')>-1) {
					// Illegal! _bezier_ and controlX/Y are protected properties and cannot have start values set.
					if (_oTemps.outputLevel > 0) {
						FuseKitCommon.error('110',(_sID()), j); // 110 changed, this was added later.
					}
					continue;
				}
				j = j.slice(6);
				se = oPr.oSP;
			}
			else {
				se = oPr.oEP;
			}
			if (ADD_UNDERSCORES==true && _oTemps.sUP.indexOf('|_'+j+'|')>-1) j='_'+j;
			if (_oTemps.sCT.indexOf('|'+j+'|')>-1) {
				var addPct:Boolean = (j=='_tintPercent' && se.colorProp.p=='_tint');
				var addTint:Boolean = (j=='_tint' && se.colorProp.p=='_tintPercent');
				if (se.colorProp==undefined || addPct==true || addTint==true) { // write only 1 color prop per profile, saves cleanup work during tween.
					if (addPct==true) se.colorProp = {p:'_tint', v:{ tint:se.colorProp.v, percent:v }};
					else if (addTint==true) se.colorProp = {p:'_tint', v:{ tint:v, percent:se.colorProp.v }};
					else se.colorProp = {p:j, v:v};
					bTwFlag = true;
				}
				else if (_oTemps.outputLevel>0) {
					FuseKitCommon.error('115',(_sID()),j);
				}
			}
			else if (v!=null) { // (null values are only accepted for color props)
				se[j] = v;
				bTwFlag = true;
			}
		} // end parse props(j), still looping through actions(i)
		
		/* Special "Nontween delay" cases
		 * (Note that normally delays are played by parent fuse except in these cases)
		 *  -start/update callbacks without any tween props
		 *  -delay within multi-item group
		 */ 
		if (bTwFlag==false && (oPr.trigger!=undefined || 
								((oPr.delay!=undefined || oPr.seconds!=undefined) &&
									 ( (oPr.startfunc!=undefined || oPr.updfunc!=undefined)
								 	   || (oPr.func!=undefined && _oTemps.nActions>1) )))) {
			if (_ZigoEngine==undefined) { // ignore output level for crucial error message
				FuseKitCommon.error('116');
			}
			else {
				if (oPr.func!=undefined) _oTemps.afl++;
				if (oPr.event!=undefined) _oTemps.ael++;
				oPr._doTimer = true;
				if (oPr.delay!=undefined) _oTemps.twDelayFlag = true;
				return oPr;
			}
		}
		if (bTwFlag==true) { // do a final round of cleanup on tween profile and build string image
			var bEC:Boolean = (oPr.oEP.colorProp!=undefined);
			for (var l:Number=0; l<2; l++) {
				var se:Object = (l==0) ? oPr.oSP : oPr.oEP;
				var str:String = (l==0) ? _oTemps.sImgS : _oTemps.sImgE;
				var sCP:String = se.colorProp.p;
				if (sCP!=undefined) { // combine color back in (colorProp was used to enforce single start/end color)
					se[sCP] = se.colorProp.v;
					delete se.colorProp;
				}
				if ((se._xscale!=undefined || se._scale!=undefined) && (se._width!=undefined || se._size!=undefined)) {
					var discard:String = (se._xscale!=undefined) ? '_xscale' : '_scale';
					delete se[discard];
					if (_oTemps.outputLevel>0) FuseKitCommon.error('115',(_sID()),discard);
				}
				if ((se._yscale!=undefined || se._scale!=undefined) && (se._height!=undefined || se._size!=undefined)) {
					var discard:String = (se._yscale!=undefined) ? '_yscale' : '_scale';
					delete se[discard];
					if (_oTemps.outputLevel>0) FuseKitCommon.error('115',(_sID()),discard);
				}
				if (se._fade!=undefined && se._alpha!=undefined) {
					delete se._alpha;
					if (_oTemps.outputLevel>0) FuseKitCommon.error('115',(_sID()),'_alpha');
				}
				for (j in se) {
					if (str.indexOf(j+', ')==-1) str+=(j+', ');
					if (se==oPr.oSP) {
						if (oPr.oEP[j]==undefined && !(j==sCP && bEC==true)) { // add 'autofill' lookup
							oPr.oAFV[j] = true;
							oPr.oEP[j] = [];
						}
					}
				}
				((l==0) ? _oTemps.sImgS = str : _oTemps.sImgE = str);
			}
			return oPr;
		}
		// If no tweens were added or start/end profiles ended up empty, move usable props to _oElements
		if (oPr.delay!=undefined && _oTemps.nActions==1) { // single-item actions only, see first if() in this block for multi-item actions
			_oElements.delay = oPr.delay;
			_oElements.delayscope = oPr.scope; // (internal, not a valid user prop!)
		}
		if (oPr.event!=undefined) {
			_oTemps.ael++;
			_oElements.aEvents.unshift({scope:oPr.scope, e:oPr.event, ep:oPr.eventparams, skipLevel:oPr.skipLevel});
		}
		// actions containing startfunc/updfunc are handled w/nonTweenDelay.
		var oldL:Number = _oElements.aEvents.length;
		if (oPr.func!=undefined) _oElements.aEvents.push({func:oPr.func, scope:oPr.scope, args:oPr.args, skipLevel:oPr.skipLevel});
		_oTemps.afl+=(_oElements.aEvents.length-oldL);
		delete oPr;
		return undefined;
	}
	
	// 3.
	
	/**
	* @exclude
	* Internal method to preset start values and generate tween calls to be sent to the engine. This is a complex procedure mainly due to the 
	* 'runtime-evaluation' feature of the Kit - scope, target(s), and all start and end values delegated to a function are retrieved by executing 
	* such functions just as the item is encountered in the sequence. doTweens handles all of this, builds and sends the tween calls while storing 
	* a reference in the internal _aTweens array. It then does emergency cleanup in situations where no tweens successfully fired and boolean end-values, 
	* callbacks, or delays are orphaned. Presetting start values is handled with 0-second tween calls which is important for interrupting any current tweens, 
	* which are monitored by any running Fuses; Start values with missing end-values are 'auto-filled' during doTweens.
	* @param targs			parent Fuse instance's current default targets
	* @param defaultScope	parent Fuse instance's current default scope
	* @param setStart		flag indicating that the doTweens call should only preset start values.		
	* @return 				returns successfully tweened props for doTween()
	*/
	private function doTweens(targs:Array, defaultScope:Object, defaultSeconds:Number, defaultEase:Object, setStart:Boolean, isFF:Boolean):String
	{
		if (_aTweens==null) {
			this._aTweens = [];
		}
		var tba:Object = _oTwBeingAdded = {};
		var ZE:Function = _ZigoEngine;
		var addTween:Function = function(target:Object, props:Object, endvals:Object, seconds:Number, ease:Object, delay:Number, callback:Object):Array {
			if (target.__zigoID__==null) ZE.initializeTargets(target);
			tba[target.__zigoID__] = true; // flag this target in _oTwBeingAdded only during the ZManager.addTween call, used by other FuseItems in the same Fuse (see onTweenEnd)
			var sProps:String = ZE.doTween.apply(ZE, arguments);
			tba[target.__zigoID__] = false;
			return ((sProps==null) ? [] : (sProps.split(',')));
		};
		var fuse:Object = _global.com.mosesSupposes.fuse.Fuse;
		var outputLevel:Number = (fuse!=undefined) ? fuse.OUTPUT_LEVEL : _ZigoEngine.OUTPUT_LEVEL;
		var propsAdded:String = '';
		var nTgErrors:Number = 0;
		var i:String, j:String, k:String;
		
		var doSetStarts:Boolean = (_bStartSet!=true && (setStart==true || _sImage.indexOf('StartProps:')>-1));
		// cycling through each action profile
		for (var h:Number=0; h<_aProfiles.length; h++) 
		{
			if (_nPlaying<2) return null; // abort if item has been stopped during doTweens
			var pr:Object = _aProfiles[h];
			
			/*
			 * Build-Mode profile item (startprops option doesn't exist here) 
			 * Do condensed tween loop. Currently lacking target error tracking.
			 * - used to have this as a separate profile loop but there are ways to mix buildmode and 
			 *   object syntax tweens in the same group.
			 */ 
			if (pr.__buildMode==true) { 
				var twArgs:Array = (_aProfiles[h]).tweenargs;
				var prevPropsAdded:String = propsAdded;
				if ((twArgs[6]).cycles===0 || ((twArgs[6]).cycles.toUpperCase())=='LOOP') {
					delete (twArgs[6]).cycles;
					if (outputLevel>0) FuseKitCommon.error('117',(_sID()));
				}
				var cb:Object = FuseKitCommon.parseCallback(twArgs[6]); // validated callback preserves cb-id across doTween calls
				if (!(twArgs[0] instanceof Array)) twArgs[0] = [ twArgs[0] ];
				for (i in twArgs[0]) {
					if (isFF==true) {
						addTween(twArgs[0][i], twArgs[1], twArgs[2], 0, null, 0, {skipLevel:0});
						if (outputLevel==3) {
							FuseKitCommon.output('\n-'+(_sID())+' FF(simple syntax)\ttargets:['+twArgs[0][i]+']\tprops:['+twArgs[1]+']');
						}
					}
					else {
						var aProps:Array = addTween(twArgs[0][i], twArgs[1], twArgs[2], twArgs[3], twArgs[4], twArgs[5], cb); 
						if (aProps.length>0) {
							_aTweens.push({targ:twArgs[0][i], props:aProps, targZID:(twArgs[0][i]).__zigoID__});
							(twArgs[0][i]).addListener(this);
							for (j in aProps) if (propsAdded.indexOf(aProps[j]+',')==-1) propsAdded+=(aProps[j]+',');
						}
						if (outputLevel==3) {
							FuseKitCommon.output('\n-'+(_sID())+' TWEEN(simple syntax)\ttargets:['+twArgs[0][i]+']\tprops tweened:['+aProps.toString()+']');
						}
					}
				}
				if (isFF==false && (prevPropsAdded==propsAdded || propsAdded=='')) nTgErrors++;
				continue;
			}
			
			var scope:Object = defaultScope; // this local is used many times within this loop for runtime-eval
			
			// targets + addTargets
			var targets:Array = []; 
			var aBase:Array = (pr.target==undefined) ? targs : pr.target; 
			var aTemp:Array = [];
			var bTgError:Boolean = false;
			// (any runtime func (or delegate) may return one targ or an array of targs)
			for (i in aBase) { 
				var v:Object = aBase[i];
				aTemp = aTemp.concat((v instanceof Function) ? v.apply(scope) : v);
			}
			for (i in pr.addTarget) {
				var v:Object = pr.addTarget[i];
				aTemp = aTemp.concat((v instanceof Function) ? v.apply(scope) : v);
			}
			for (i in aTemp) {
				var v:Object = aTemp[i];
				if (v!=null) {
					var exists:Boolean = false;
					for (j in targets) {
						if (targets[j]==v) {
							exists = true;
							break;
						}
					}
					if (exists==false) targets.unshift(v);
				}
				else {
					bTgError = true;
				}
			}
			var doTimer:Boolean = (pr._doTimer==true && targets.length==0 && isFF==false);
			if (bTgError==true || (targets.length==0 && pr._doTimer!=true)) {
				nTgErrors++;
			}
			// -- 1. start props --
			if (doSetStarts==true)
			{
				// generate one ZigoEngine.doTween() call per target. Props parsed for runtime-eval, bools, validation
				for (i in targets) { 
					if (_nPlaying<2) return null; // abort if item has been stopped during doTweens
					var targ:Object = targets[i];
					var aSP:Array = [];
					var aSV:Array = [];
					if (setStart==true) {
						for (var q:String in pr.oEP) {
							// Use 3rd param createNew to pre-create any missing filters on setStartProps, so the filter doesn't suddenly appear just before that action starts.
							_global.com.mosesSupposes.fuse.FuseFMP.getFilterProp(targ,q,true); 
						}
					}
					for (var p:String in pr.oSP) {
						var v:Object = pr.oSP[p];
						if (v instanceof Function) v = v.apply(scope);
						if (v===true || v===false) { // set start booleans immediately. 
							targ[p] = v;
							if (pr.oAFV[p]==true) { // prohibit autofill with booleans.
								for (k in pr.oEP[p]) if (pr.oEP[p][k].targ==targ) pr.oEP[p].splice(Number(k),1);
								pr.oEP[p].push({targ:targ,val:'IGNORE',_isAF:true});
							}
							continue;
						}
						// autofill missing end values. Assume resets for known properties, otherwise take a snapshot of the current value to return to.
						if (pr.oAFV[p]==true && !(p=='_colorReset' && v==100) && !(p=='_tintPercent' && v==0)) {// if no endprop was passed for startprop, store current value (does not apply to color prop which resets, preset during constructor parse)
							var afv:Object;
							if (p=='_tint' || p=='_colorTransform') {
								afv = _ZigoEngine.getColorTransObj();
							}
							else if (String(FuseKitCommon._resetTo100()).indexOf('|'+p+'|')>-1 || (p=='_fade' && v<50)) {
								afv = 100;
							}
							else if (String(FuseKitCommon._resetTo0()).indexOf('|'+p+'|')>-1 || p=='_fade') { // (that is, _fade && v>=50, covered by the conditional)
								afv = 0;
							}
							else { // snapshot current val, retaining target (which is important in the case of relative endvals)
								var fmpVal:Number = _global.com.mosesSupposes.fuse.FuseFMP.getFilterProp(targ,p,true);
								if (fmpVal!=null) afv = fmpVal;
								else afv = (_global.isNaN(targ[p])==false) ? targ[p] : 0;
							}
							for (k in pr.oEP[p]) if (pr.oEP[p][k].targ==targ) pr.oEP[p].splice(Number(k),1);
							pr.oEP[p].push({targ:targ,val:afv,_isAF:true});
						}
						if (typeof v=='object') {
							var copy:Object = (v instanceof Array) ? ([]) : {};
							for (k in v) copy[k] = ((v[k]) instanceof Function) ? Function(v[k]).apply(scope) : v[k];
							v = copy;
						}
						aSP.push(p);
						aSV.push(v);
					}// end startprops loop(n)
					
					// set starts using egine, which monitors/broadcasts interrupts + parses complex properties.
					if (aSV.length>0) {
						if (outputLevel==3) FuseKitCommon.output((_sID())+' '+targ+' SET STARTS: '+['['+aSP+']', '['+aSV+']']);
						addTween(targ, aSP, aSV, 0);
					}
				} // end set starts
			}// end if startprops
			
			if (setStart==true) {
				continue;
			}
			
			// prep for end props loops
			var event:Object, skipLevel:Number, oSimpleCB:Object, oCB:Object, triggerTrue:Boolean, triggerTime:Number;			
			var cbstr:String='';
			if (isFF==false) {
				// Each action may define a "scope" param which overrides Fuse default scope
				// (scope cascades to updscope & startscope if omitted.)
				if (pr.scope!=undefined) {
					// NOTE: Removed runtime-eval support for scope, because it messes up static function calls
					// such as scope:FuseFMP, func:'removeFilter' -- since the class reference is actually a function.
					scope = pr.scope; //(pr.scope instanceof Function) ? pr.scope.apply(scope) : pr.scope;
				}
				// build callback object
				skipLevel = ((pr.skipLevel instanceof Function) ? pr.skipLevel.apply(scope) : pr.skipLevel);
				var extra1:Number = ((pr.extra1 instanceof Function) ? pr.extra1.apply(scope) : pr.extra1);
				var extra2:Number = ((pr.extra2 instanceof Function) ? pr.extra2.apply(scope) : pr.extra2);
				var roundResults:Number = ((pr.roundResults instanceof Function) ? pr.roundResults.apply(scope) : pr.roundResults);
				oSimpleCB = { skipLevel:skipLevel, extra1:extra1, extra2:extra2, roundResults:roundResults };
				oCB = { skipLevel:skipLevel, extra1:extra1, extra2:extra2, roundResults:roundResults };
				if (pr.cycles!=undefined) {
					var cycles:Object = ((pr.cycles instanceof Function) ? pr.cycles.apply(scope) : pr.cycles);
					if ((Number(cycles)==0 || (String(cycles).toUpperCase())=='LOOP') && fuse!=undefined) {
						delete pr.cycles;
						if (outputLevel>0) FuseKitCommon.error('117',(_sID())); // infinite cycles not allowed in fuses
					}
					else {
						oSimpleCB.cycles = oCB.cycles = cycles;
					}
				}
				if (pr.func!=undefined || pr.startfunc!=undefined || pr.updfunc!=undefined) {
					for (i in pr) {
						if (i.indexOf('func')>-1) {
							oCB[i] = pr[i];
						}
						else if (i=='startscope' || i=='updscope' || i.indexOf('args')>-1) {
							// see note above under pr.scope (runtime eval support removed for scopes)
							oCB[i] = pr[i]; //(pr[i] instanceof Function) ? Function(pr[i]).apply(scope) : pr[i];
						}
					}
					if (scope!=undefined) {
						if (oCB.func!=undefined && oCB.scope==undefined) oCB.scope = scope; // auto-fill callback scopes where possible
						if (oCB.updfunc!=undefined && oCB.updscope==undefined) oCB.updscope = scope;
						if (oCB.startfunc!=undefined && oCB.startscope==undefined) oCB.startscope = scope;
					}
				}
				for (j in oCB) cbstr+=(j+':'+oCB[j]+'|');
				// build event object (leave after callback)
				if (pr.event!=undefined) {
					event = {scope:pr.scope, e:pr.event, ep:pr.eventparams, skipLevel:skipLevel};
				}
				// trigger: may be boolean, number, or runtime-eval function
				triggerTrue = (pr.trigger===true);
				triggerTime = undefined;
				if (triggerTrue==false && pr.trigger!=undefined) {
					triggerTime = ((pr.trigger instanceof Function) ? pr.trigger.apply(scope) : pr.trigger);
					if (typeof triggerTime=='string') {
						triggerTime = ((String(triggerTime).charAt(0)=='-') ? -(parseClock(String(triggerTime).slice(1))) : (parseClock(String(triggerTime))));
					}
					if (_global.isNaN(triggerTime)==true) triggerTime = undefined;
				}
			}// end isFF==false
			
			// -- 2. end props --
			// Generate one ZigoEngine.doTween() call per target since envals can differ due to auto-fill feature. 
			// the last round of evaluating seconds, delay, and booleans is saved for the pickup round that happens after this loop
			var delay:Number, seconds:Number, ease:Object;
			var booleans:Object;// Group end-booleans per target to be set in onTweenEnd
			var tweenSuccess:Boolean = false;
			var targsOrProxy:Array = ((doTimer==false) ? targets : [0]);// force loop to occur once for targetless timer tweens
			var nBezError:Number = -1; // limit error to once per profile, all similar errors are thrown in parseProfile.
			for (i in targsOrProxy) {
				if (_nPlaying<2) return null; // abort if item has been stopped during doTweens
				if (isFF==false) {
					// Eval other per-profile params (putting this in the loop is less efficient but it allows runtime-eval-funcs to be run per target.)
					// Q: when a user builds an action and there are multiple targets, do they want getters to be fired per target, or per action?
					if (pr.ease!=null) {
						ease = pr.ease;
						if (ease instanceof Function) { // could be a runtime eval function!
							var ef:Function = (Function(ease));
							if (typeof ef(1,1,1,1)!='number') ease = ef.apply(scope); // it's not a valid easing equation the engine can use.
						}
					}
					if (ease==null) ease = defaultEase;
					seconds = (pr.seconds instanceof Function) ? pr.seconds.apply(scope) : pr.seconds;
					if (seconds!=undefined) {
						if (typeof seconds=='string') seconds = (parseClock(String(seconds)));
						if (_global.isNaN(seconds)==true) seconds = (_ZigoEngine.DURATION || 0);
					}
					if (seconds==null) seconds = defaultSeconds;
					delay = (pr.delay instanceof Function) ? pr.delay.apply(scope) : pr.delay;
					if (typeof delay=='string') delay = (parseClock(String(delay)));
					if (delay==null || _global.isNaN(delay)==true) delay = 0;
					if (doTimer==true) continue;
				}
				// ---------------------------------------------------------------
				//parse
				var targ:Object = targsOrProxy[i];
				var aEP:Array = [];
				var aEV:Array = [];
				var numBools:Number = 0;
				var bezIndex:Number = -2; // -2=none,-1=loose props,uint=existing _bezier_ param
				for (var p:String in pr.oEP) {
					var v:Object = pr.oEP[p];
					if (v instanceof Function) {
						v = v.apply(scope);
					}
					if (v===true || v===false) { // end booleans will be attached to a tween & set in onTweenEnd.
						if (booleans==undefined) booleans = {};
						booleans[p] = v;
						numBools++;
						continue;
					}
					if (typeof v=='object') {
						if ((v[0])._isAF==true) { // this is an autofilled property - match targets.
							for (k in v) {
								if ((v[k]).targ==targ) {
									v = (v[k]).val;
									break;
								}
							}
						}
						else { 
							var copy:Object = (v instanceof Array) ? ([]) : {};
							for (k in v) copy[k] = ((v[k]) instanceof Function) ? Function(v[k]).apply(scope) : v[k];
							v = copy;
						}
					}
					if (v!='IGNORE') {
						if (p=='_bezier_') bezIndex = aEP.length;
						else if (bezIndex==-2 && (p=='controlX' || p=='controlY')) bezIndex = -1;
						aEP.push(p);
						aEV.push(v);
					}
				} // end endprops loop(n)
				
				if (aEV.length>0) {
					if (bezIndex>-2) {
						// Build bezier after all parsing is complete. This ordering enables start_x & start_y + autofill functionality. (Moved here from parseProfile in v2.1.1r2)
						if (bezIndex==-1) bezIndex = aEP.length;
						aEP[bezIndex] = '_bezier_';
						if (typeof aEV[bezIndex]!='object') aEV[bezIndex] = {}; // might have been defined by user
						var bezObj:Object = aEV[bezIndex]; // set a reference since the index gets messed up during splice.
						for (j in aEP) { // splice x,y out of end props and profile object into bezier object.
							if (('|x|y|_x|_y|controlX|controlY|').indexOf('|'+aEP[j]+'|')>-1) {
								if (aEP[j].charAt(0)=='_') aEP[j] = aEP[j].slice(-1);
								if (typeof bezObj[aEP[j]] == 'number') { // collision: similar property was already included in a _bezier_ parameter in the same action. Discard the loose properry
									if (outputLevel>0 && (nBezError==-1 || nBezError==i)) {
										FuseKitCommon.error('115', (_sID()), aEP[j]);
										nBezError = Number(i);
									}
								}
								else {
									bezObj[aEP[j]] = aEV[j];
								}
								aEP.splice(Number(j), 1);
								aEV.splice(Number(j), 1);
							}
						}
					}
					if (isFF==true) {
						if (outputLevel==3) FuseKitCommon.output('\n-'+(_sID())+' FF\ttargets:['+targ+']\tprops:['+aEP.toString()+']');
						addTween(targ, aEP, aEV, 0, null, 0, { skipLevel:0 });
						continue;
					}
					// normal tweens. monitor only the props that are added to the engine's tweenlist.
					// Only add callbacks/booleans/events in the case of single target - otherwise these things will be picked up with a fake tween later.
					// listener: fixes an obscure issue where doTween returns null but has fired callbacks/events, which can happen during 0-second, skipLevel:0 tweens, also when ZigoEngine.TIME_MULTIPLIER=0.
					// Provides a third option where callbacks are stripped. (note: tests seem to prove that asbroadcaster is fast enough to make this work although in theory the event might be dispatched too late to show up.)
					var listener:Object = { caught:false, onTweenEnd:function(evto:Object) { (this).caught = true; } };
					targ.addListener(listener);
					// ~ THE TWEEN CALL ~
					var aProps:Array = addTween(targ, aEP, aEV, seconds, ease, delay, oCB);
					// ~ ~ ~ ~ ~ ~ ~ ~ ~
					targ.removeListener(listener);
					if (aProps.length==0) {
						if (listener.caught==true) { oCB = oSimpleCB; }
					}
					else {
						if (aProps.length>0) {
							var to:Object = {targ:targ, props:aProps, bools:booleans, targZID:targ.__zigoID__};
							if (tweenSuccess==false) {
								// Important: callback, event, trigger:true need to only be added once per profile.
								oCB = oSimpleCB;
								to.event = event;
								event = booleans = undefined;
								to.trigger = triggerTrue; // single-target profile with trigger:true
							}
							_aTweens.push(to);
							targ.addListener(this);
							tweenSuccess = true;
							for (j in aProps) if (propsAdded.indexOf(aProps[j]+',')==-1) propsAdded+=(aProps[j]+',');
						}
						// TRACE TWEENS (lets you know if one or more props didn't get added)
						if (outputLevel==3) {
							var epstr:String = aEP.toString();
							if (aProps.length>aEP.length) epstr += ('\n\t[NO-CHANGE PROPS DISCARDED (disregard this for double props like _scale). KEPT:'+aProps.toString()+']');
							var evstr:String = '';
							for (j in aEV) evstr=(((typeof aEV[j]=='string')?'"'+aEV[j]+'"':aEV[j]) + ', '+evstr);
							FuseKitCommon.output('\n-'+(_sID())+' TWEEN:\n'+(['\t[getTimer():'+getTimer()+'] ','targ: '+targ, 'props: '+epstr, 'endVals: '+evstr, 'time: '+((seconds==undefined) ? _ZigoEngine.DURATION : seconds), 'easing: '+((ease==undefined) ? _ZigoEngine.EASING : ease), 'delay: '+((delay==undefined) ? 0 : delay), 'callbacks: '+((cbstr=='') ? '(none)':cbstr)]).join('\n\t')); 
						}
					}
					listener = undefined;
				}
			} // end targets loop(i)
			if (_global.isNaN(seconds)==true || pr.seconds==null) seconds = 0;
			var time:Number = delay+seconds;
			
			// time-based trigger: use a fake tween (trigger time must be less than tween duration+delay)
			if (triggerTime!=undefined) {
				// Special option to use a negative trigger time, which is counted back from the end of the tween
				if (triggerTime<0) {
					triggerTime += time;
				}
				if (triggerTime>0 && (time==0 || triggerTime<time)) {
					if (time==0) {
						// action has trigger plus callback/event/booleans but no delay or tween time - include these things in the trigger delay.
						if (outputLevel==3) { FuseKitCommon.output((_sID())+' graft a timed trigger ('+triggerTime+' sec). [has callback:'+(oCB!=oSimpleCB)+', has event:'+(event!=undefined)+', has booleans:'+(booleans!=undefined)+']'); }
						doTimerTween(null, triggerTime, 0, true, booleans, oCB, event);
						tweenSuccess = true;
					}
					else {
						// action has trigger plus a delay or other tweens, create a separate trigger delay and save other items for final pickup
						if (outputLevel==3) { FuseKitCommon.output((_sID())+' graft a timed trigger ('+triggerTime+' sec).'); }
						doTimerTween(null, triggerTime, 0, true);
					}
				}
				else if (outputLevel==3) { FuseKitCommon.output((_sID())+' timed trigger discarded: out of range. ['+triggerTime+'/'+time+']'); }
			}
			
			// If nothing tweened, pick up any booleans, callbacks, and custom event.
			if (tweenSuccess==false && (oCB!=oSimpleCB || event!=undefined || booleans!=undefined)) {
				if (skipLevel==0 && time>0) { // run a tweened delay before doing callbacks, booleans & events
					if (outputLevel==3) { FuseKitCommon.output((_sID())+' no props tweened - graft a delay ('+time+' sec). [has callback:'+(oCB!=oSimpleCB)+', has event:'+(event!=undefined)+', has booleans:'+(booleans!=undefined)+']'); }
					doTimerTween(targets, seconds, delay, triggerTrue, booleans, oCB, event);
				}
				else { // immediate wrap-up
					if (outputLevel==3) { FuseKitCommon.output((_sID())+' no props tweened, executing nontween items. [has callback:'+(oCB!=oSimpleCB)+', has event:'+(event!=undefined)+', has booleans:'+(booleans!=undefined)+']'); }
					for (i in targets) {
						for (j in booleans) targets[i][j] = booleans[j]; // set end booleans
					}
					if (skipLevel<2) {// If skipLevel is 2 in all actions and no tweens succeed it will go unfired.
						if (oCB!=undefined) {
							fireEvents(oCB,scope,outputLevel,targets);
						} 
						if (event!=undefined) {
							fireEvents(event,scope,outputLevel);
						}
					}
				}
			}
		} // end profiles loop(h)
		if (nTgErrors>0 && outputLevel>0) {
			if (nTgErrors==_aProfiles.length && propsAdded=='') FuseKitCommon.error('118',(_sID()),setStart);
			else FuseKitCommon.error('119',(_sID()),nTgErrors,doSetStarts);
		}
		tba = undefined;
		_oTwBeingAdded = undefined;
		return ((propsAdded=='') ? null : propsAdded.slice(0,-1));
	}
	
	/**
	 * @exclude
	 */
	private function doTimerTween(actualTargets:Array, duration:Number, delay:Number, trigger:Boolean, booleans:Object, callback:Object, event:Object):Void
	{
		var proxy:Object = { __TweenedDelay:0 };
		_ZigoEngine.initializeTargets(proxy);
		_aTweens.push({targ:proxy, props:['__TweenedDelay'], trigger:trigger, bools:booleans, event:event, actualTargs:actualTargets, targZID:proxy.__zigoID__ });
		var canceled:Boolean = ( _ZigoEngine.doTween(proxy, '__TweenedDelay', 1, duration, null, delay, callback) == null);
		if (canceled==true) {
			onTweenEnd({ target:proxy, props:['__TweenedDelay'] });
		}
		else {
			proxy.addListener(this);
		}
	}
	
	/**
	* @exclude
	* An event dispatched by each animation target via the engine. This method dissects the target and properties sent in the event object, 
	* looking for a match in the internal tween array <code>_aTweens</code>, which is progressively deleted. Also handles tweens tagged with a 
	* trigger property and sets boolean end values on associated tween completion.
	* @param o			event object containing target and completed-property information
	*/
	private function onTweenEnd(o:Object, doAutoStop:Boolean):Void
	{
		if (_nPlaying<1) return;
		var fuse:Object = _global.com.mosesSupposes.fuse.Fuse;
		var outputLevel:Number = (fuse!=undefined) ? fuse.OUTPUT_LEVEL : _ZigoEngine.OUTPUT_LEVEL;
//		if (outputLevel==3) FuseKitCommon.output((_sID())+' onTweenEnd..[verify below if any props removed] '+((typeof o.target=='movieclip')?o.target._name:(typeof o.target))+'['+o.props+'] [getTimer()='+getTimer()+']');
		var id:Number = ((o.__zigoID__!==undefined) ? o.__zigoID__ : o.target.__zigoID__);
		for (var i:String in _aTweens) {
			var to:Object = _aTweens[i];
			// safer to match ids since pause(resume) might be trying to clear out a missing target
			if (to.targZID==id) {  
				for (var j:String in o.props) {
					var pa:Array = to.props;
					for (var k:String in pa) {
						var p:String = pa[k]; 
						if (p==o.props[j]) { 
							pa.splice(Number(k), 1);
//							if (outputLevel==3) FuseKitCommon.output((_sID())+' onTweenEnd is removing prop: '+p);
							if (doAutoStop==true) {
								// Auto-Stop feature requires some finesse: If the Fuse is just overwriting one of its own post-trigger trailing tweens, we need to ignore the interruption.
								// Methodology: Peek at the parent Fuse's active item and check its _oTwBeingAdded var, which is a temp list of targets only set during doTween calls.
								// The interruption obj's zID must match, and during:'add' verifies that this interruption happened during ZMgr.addTween process.
								var inst:Object = fuse.getInstance(_nFuseID);
								var interruptedByLocalFuse:Boolean = (_bTrigger==true && o.during=='add' && inst[inst.currentIndex]._oTwBeingAdded[id]===true && inst.state=='playing');
								if (interruptedByLocalFuse==false) {
									to.targ.removeListener(this);
									// Quickly strip all other props being interrupted from _aTweens so they aren't messed with in the stop cycle 
									// (this formula same as the main loop, reusing the vars since it returns out)
									for (i in _aTweens) {
										if (_aTweens[i].targZID==id) {
											for (j in o.props) {
												for (k in _aTweens[i].props) if (_aTweens[i].props[k]==o.props[j]) _aTweens[i].props.splice(Number(k), 1);
											}
											if (_aTweens[i].props.length==0) _aTweens.splice(Number(i),1);
										}
									}
									if (outputLevel==3) FuseKitCommon.output((_sID())+' triggering auto-stop due to interruption');
									if (inst.autoClear==true || (inst.autoClear!==false && fuse.AUTOCLEAR==true)) {
										dispatchRequest('destroy');
									} else {
										dispatchRequest('stop');
									}
									return;
								}
								else if (outputLevel==3) {
									FuseKitCommon.output('note -'+(_sID())+' interrupted one of its own properties "'+p+'". (Autostop not triggered.)');
								}
							}
							if (_nPlaying==2 && p!='__TweenedDelay') {
								// interruption during doTweens looping 
								// (don't throw errors for proxy tweens. ZigoEngine.TIME_MULTIPLIER:0 causes these tweens to run onTweenEnd during add.)  
								if (outputLevel>0) FuseKitCommon.error('120',(_sID()),p);
							}
							if (pa.length==0) {
								if (to.event!=undefined) {
									fireEvents(to.event, null, outputLevel);
								}
								if (p=='__TweenedDelay') {
									_ZigoEngine.deinitializeTargets(to.targ);
									delete to.targ; // try and dispose of the temp object
									// set boolean end vals to cached targets. (listeners: in theory if the fake tween passed targets at all it is because no tweens in this target group were added, therefore listeners should not have been added on the actual targets.)
									for (var m:String in to.bools) {
										for (var t:String in to.actualTargs) {
											to.actualTargs[t][m] = to.bools[m];
										}
									}
								}
								else {
									var found:Boolean = false;
									for (var m:String in to.bools) { // set boolean end vals
										to.targ[m] = to.bools[m];
									}
									for (var l:String in _aTweens) {
										if (l!=i && (_aTweens[l]).targ==to.targ) found = true;
									}
									if (found==false) { // target done
										to.targ.removeListener(this);
									}
								}
								if (to.trigger==true) {
									if (_bTrigger==false && o.isResume!=true && _aTweens.length>1) {
										_bTrigger = true;
										if (outputLevel==3) FuseKitCommon.output((_sID())+' trigger fired!');
										var breakChainInt:Number;
										breakChainInt = setInterval(function(fi:FuseItem) {
											clearInterval(breakChainInt);
											fi.dispatchRequest('advance',[false,false,false]);
										},1,this);
									}
								}
								_aTweens.splice(Number(i),1);
							}
						}
					}
				}
			}
		}
		if (_aTweens.length==0 && _nPlaying==1 && o.isResume!=true) { // tweens can be removed during pause(0) but don't advance.
			complete(outputLevel);
		}
	}
	
	/**
	* @exclude
	* An event dispatched by the ZigoEngine class when a target is removed or a tween is overwritten.
	* @param o	event object containing target and interrupted-property information
	*/
	private function onTweenInterrupt(o:Object):Void
	{
		if (_nPlaying==-1) return;
		var fuse:Object = _global.com.mosesSupposes.fuse.Fuse;
		var parentfuse:Object = fuse.getInstance(_nFuseID);
		var autoStop:Boolean = (parentfuse.autoStop==true || (parentfuse.autoStop!==false && fuse.AUTOSTOP==true));
		var id:Number = o.__zigoID__;
		var outputLevel:Number = (fuse!=undefined) ? fuse.OUTPUT_LEVEL : _ZigoEngine.OUTPUT_LEVEL;
//		if (outputLevel==3) FuseKitCommon.output((_sID())+' property interrupt caught..[verify below if any props removed] '+o.target+',__zigoID__:'+id+'['+o.props+'].');
		if (autoStop==true || (autoStop!==false && fuse.AUTOSTOP==true)) {
			onTweenEnd(o,true);
			return;
		}
		if (typeof o.target!='string') { // string [MISSING.. is passed in onTweenInterrupt when targets go missing.
			onTweenEnd(o);
			return;
		}
		for (var i:String in _aTweens) { // target went missing, quickly strip out all _aTween objs with that target
			if ((_aTweens[i]).targZID==id) {
				_aTweens.splice(Number(i),1);
			}
		}
		if (_aTweens.length==0 && _nPlaying==1) {
			complete(outputLevel);
		}
	}
	
	/**
	* @exclude
	* Internal method fired when the Fuse should advance.
	* @param outputLevel
	*/
	private function complete(outputLevel:Number):Void
	{
		if (outputLevel==3) FuseKitCommon.output((_sID())+' complete.');
		// Use a setInterval for callback-only items to avoid a continuous action list which can lock the player
		// Special cases are made for overlap* which can occur when stop or pause commands are called on the Fuse
		// in external tween-end callbacks. (updated v2.1.2)
		var breakChainInt:Number;
		breakChainInt = setInterval(function(fi:FuseItem,trigger:Boolean) {
			clearInterval(breakChainInt);
			var itemstate:Number = fi._nPlaying;
			if (trigger!=true) {
				if (itemstate<1) { // *Overlap (note above)
					return;
				}
			}
			fi.stop(); // will reset _nPlaying which is read by Fuse on last item (Triggered items are allowed to stop in all cases.)
			if (itemstate>0) { // *Overlap (note above) 
				fi.dispatchRequest('advance',[trigger,false,false]);
			}
		},1,this,_bTrigger);
	}
	
	/**
	* @exclude
	* Internal method that parses 'timecode' style strings that user may pass for duration or delay. Strings must be in "00:00" format meaning seconds:hundreths.
	* @param str		the string value to be parsed into a numerical duration representing seconds
	* @return			time in seconds
	*/
	private function parseClock(str:String):Number 
	{ 
		if (str.indexOf(':')!=2) {
			FuseKitCommon.error('121');
			return (_ZigoEngine.DURATION || 0);
		}
		var time:Number = 0;
		var spl:Array = str.split(':');
		spl.reverse();
		var t:Number;
		if (String(spl[0]).length==2 && _global.isNaN(t = Math.abs(Number(spl[0])))==false) time += (t/100);// hundredths
		if (String(spl[1]).length==2 && _global.isNaN(t = Math.abs(Number(spl[1])))==false && t<60) time += t;// seconds
		if (String(spl[2]).length==2 && _global.isNaN(t = Math.abs(Number(spl[2])))==false && t<60) time += (t*60);// minutes
		if (String(spl[3]).length==2 && _global.isNaN(t = Math.abs(Number(spl[3])))==false && t<24) time += (t*3600);// hours - hah!
		return time;
	}
	
	/**
	* @exclude
	* Internal method used to both trigger custom Fuse events and freestanding callbacks not handled by the engine.
	* @param o				condensed object with relevant information like scope, functions, arguments, event names, etc.
	* @param scope			fuse default scope
	* @param outputLevel	current outputLevel
	* @param targets		used to locate unscoped functions
	*/
	private function fireEvents(o:Object,scope:Object,outputLevel:Number,targets:Array):Void
	{
		if (o.scope==undefined) o.scope = scope;
		if (o.e==undefined) { // callback
			var callback:Object = FuseKitCommon.parseCallback(o, targets, outputLevel, false);
			if (callback.start.f!=null) callback.start.f.apply(callback.start.s, callback.start.a);
			if (callback.upd.f!=null) callback.upd.f.apply(callback.upd.s, callback.upd.a);
			if (callback.end.f!=null) callback.end.f.apply(callback.end.s, callback.end.a);
		}
		else { // event (scope used only for runtime-eval)
			var type:String = (o.e instanceof Function) ? String(o.e.apply(scope)) : String(o.e);
			if (type!='undefined' && type.length>0) {
				if (String(FuseKitCommon._fuseEvents()).indexOf('|'+type+'|')>-1) {
					if (outputLevel>0) FuseKitCommon.error('122',type);
				}
				else {
					var fuse:Object = _global.com.mosesSupposes.fuse.Fuse.getInstance(_nFuseID);
					var evObj:Object = (o.ep instanceof Function) ? o.ep.apply(scope) : o.ep;
					if (evObj==null || typeof evObj!='object') evObj = {};
					evObj.target = fuse;
					evObj.type = type;
					fuse.dispatchEvent.call(fuse,evObj); // make the Fuse dispatch the event
				}
			}
			else if (outputLevel>0) {
				FuseKitCommon.error('123',(_sID()));
			}	
		}
	}
}